<?php
/**
 * 
 * @author 
 * @see
 */
class CK_Template
{
	var $reuse_code		= true;
	var $base_dir       = '';
	var $template_dir	= '/application/view/'; //
	var $temp_dir		= '/_cache/_compiled/';
	var $cache_dir      = '/_cache/';
	var $cache_lifetime =  600;
	var $cache_filename ;
	var $tpl_file;
	var $cpl_file;
	var $data = array();
	var $parser;
	
	/**
	 * 构造函数
	 * @param $template_filename
	 */
	function CK_Template ($config = array())
	{
		global $ck_config;
		$template_filename = isset($config['file_name'])?$config['file_name']:'';
		$this->base_dir = isset($config['base_dir'])?$config['base_dir']:'';
		$this->template_dir  = isset($ck_config['view_dir'])? $this->base_dir . $ck_config['view_dir']:$this->base_dir .$this->template_dir;
		$this->temp_dir = isset($ck_config['view_cache_compiled_dir'])? $this->base_dir . $ck_config['view_cache_compiled_dir']:$this->base_dir .$this->temp_dir;
		$this->cache_dir  = isset($ck_config['view_cache_dir'])? $this->base_dir . $ck_config['view_cache_dir']:$this->base_dir .$this->cache_dir;

        if(!file_exists($this->cache_dir))
        {
            @mkdir($this->cache_dir,DIR_WRITE_MODE);
        }
        if(!file_exists($this->temp_dir))
        {
            @mkdir($this->temp_dir,DIR_WRITE_MODE);
        }
		if (is_numeric($ck_config['view_cache_lifetime']))
		{
			$this->cache_lifetime  = $ck_config['view_cache_lifetime'];
		}
		$this->tpl_file  =  $template_filename;
	}

	function set_templatefile ($template_filename)	
	{	
	    $this->tpl_file  =  $template_filename;	
	}
	
	function add_value ($name, $value )				
	{
	    $this->assign($name, $value);	
	}
	
    
	/**
	 * 为模板赋值,第一个参数可以为数组,若不为数组,则令$name=>$value
	 * @param $name
	 * @param $value
	 * @return none
	 */
	function assign ( $name, $value = '' )
	{
		if (is_array($name))
		{
			foreach ($name as $k => $v)
			{
				$this->data[$k]  =  $v;
			}
		}
		else
		{
			$this->data[$name]  =  $value;
		}
	}

	/**
	 * 在原来的数据基础上增加新值
	 * @param mixed $name
	 * @param mixed $value 
	 * @return none
	 */
	function append ( $name, $value )
	{
		if (is_array($value))
		{
			$this->data[$name][]  =  $value;
		}
		elseif (!is_array($this->data[$name]))
		{
			$this->data[$name]  .=  $value;
		}
	}

	/**
	 * 
	 * @param string $_top
	 * @return 
	 */
	function result ( $_top = '' )
	{
		ob_start();//之前已经调用过一次,但是不影响这里的调用
		$this->output( $_top );
		$result  =  ob_get_contents();
		ob_end_clean();
		return $result;
	}

	/**
	 * 
	 * @param $_top
	 */
	function output ( $_top = '' )
	{
		global $_top;
		
		/*为目录加上 斜杠 */
		if ($this->template_dir != ''  &&  substr($this->template_dir, -1) != '/')
		{
			$this->template_dir  .=  '/';
		}
		if ($this->temp_dir != ''  &&  substr($this->temp_dir, -1) != '/')
		{
			$this->temp_dir  .=  '/';
		}
		
		if (!is_array($_top))
		{
			if (strlen($_top))
			{
				$this->tpl_file  =  $_top;
			}
			$_top  =  $this->data;
		}
		$_obj  =  &$_top;//实际上是保存模板的变量
		$_stack_cnt  =  0;
		$_stack[$_stack_cnt++]  =  $_obj;

		$cpl_file_name = preg_replace('/[:\/.\\\\]/', '_', $this->tpl_file);
		if (strlen($cpl_file_name) > 0)
		{
    		$this->cpl_file  =  $this->temp_dir . $cpl_file_name . '.php';
			$compile_template  =  true;
			if ($this->reuse_code)
			{
				if (is_file($this->cpl_file))
				{
				    /*如果编译后的PHP文件比原来的html文件更新,那么就不用重新编译,否之则要 */
					if ($this->mtime($this->cpl_file) > $this->mtime($this->template_dir . $this->tpl_file))
					{
						$compile_template  =  false;
					}
				}
			}
			if ($compile_template)
			{
				$this->parser = new CK_TemplateParser($this->template_dir . $this->tpl_file);
				if (!$this->parser->compile($this->cpl_file))
				{
					exit( "Template Parser Error: " . $this->parser->error );
				}
			}
			include($this->cpl_file);//加载编译后的PHP文件
		}
		else
		{
			exit( "Template Error: You must set a template file name");
		}

		unset ($_top);//销毁模板变量
	}

	/**
	 * 当没有post数据的时候,可以考虑使用缓存
	 * @param $key
	 */
	function use_cache ( $key = '' )
	{
		if (empty($_POST))
		{
			$this->cache_filename  =  $this->cache_dir . 'cache_' . md5(serialize($key)) . '.ser';
			if (@is_file($this->cache_filename))
			{
				if ((time() - filemtime($this->cache_filename)) < $this->cache_lifetime)
				{
					readfile($this->cache_filename);
					exit;
				}
			}
			ob_start( array( &$this, 'cache_callback' ) );
		}
	}

	/**
	 * 
	 * @param $output
	 */
	function cache_callback ( $output )
	{
		if ($hd = @fopen($this->cache_filename, 'w'))
		{
			fputs($hd,  $output);
			fclose($hd);
		}
		return $output;
	}

	/**
	 * 获取文件的更新时间
	 * TODO 返回值不全面
	 * @param string $filename
	 */
	function mtime ( $filename )
	{
		if (@is_file($filename))
		{
			$ret = filemtime($filename);
			return $ret;
		}
	}
}

/**
 * 
 * @author 
 *
 */
class CK_TemplateParser
{
	var $template;
	var $template_dir;
	var $error;

	function CK_TemplateParser ( $template_filename )
	{
		if ($hd = @fopen($template_filename, "r"))
		{
			if (filesize($template_filename))
			{
				$this->template = fread($hd, filesize($template_filename));
			}
			else
			{
				$this->template = "Template Parser Error: File size is zero byte: '$template_filename'";
			}
			fclose($hd);
			$this->template_dir = dirname($template_filename);
		}
		else
		{
			$this->template = "Template Parser Error: File not found: '$template_filename'";
		}
	}

	function compile( $compiled_template_filename = '' )
	{
		if (empty($this->template))
		{
			return;
		}

		if(preg_match("/<!-- INCLUDE/i", $this->template))
		{
			while ($this->count_subtemplates() > 0)
			{
				preg_match_all('/<!-- INCLUDE ([a-zA-Z0-9_.]+) -->/', $this->template, $tvar);
				foreach($tvar[1] as $subfile)
				{
					if(file_exists($this->template_dir . "/$subfile"))
					{
						$subst = implode('',file($this->template_dir . "/$subfile"));
					}
					else
					{
						$subst = 'Template Parser Error: Subtemplate not found: \''.$subfile.'\'';
					}
					$this->template = str_replace("<!-- INCLUDE $subfile -->", $subst, $this->template);
				}
			}
		}

		$page  =  preg_replace("/<!-- ENDIF.+?-->/",  "<?php\n}\n?>",  $this->template);
		$page  =  preg_replace("/<!-- END[ a-zA-Z0-9_.]* -->/",  "<?php\n}\n\$_obj=\$_stack[--\$_stack_cnt];}\n?>",  $page);
		$page  =  str_replace("<!-- ELSE -->",  "<?php\n} else {\n?>",  $page);

		if (preg_match_all('/<!-- BEGIN ([a-zA-Z0-9_.]+) -->/', $page, $var))
		{
			foreach ($var[1] as $tag)
			{
				list($parent, $block)  =  $this->var_name($tag);
				$code  =  "<?php\n"
						. "if (!empty(\$$parent"."['$block'])){\n"
						. "if (!is_array(\$$parent"."['$block']))\n"
						. "\$$parent"."['$block']=array(array('$block'=>\$$parent"."['$block']));\n"
						. "\$_tmp_arr_keys=array_keys(\$$parent"."['$block']);\n"
						. "if (\$_tmp_arr_keys[0]!='0')\n"
						. "\$$parent"."['$block']=array(0=>\$$parent"."['$block']);\n"
						. "\$_stack[\$_stack_cnt++]=\$_obj;\n"
						. "foreach (\$$parent"."['$block'] as \$rowcnt=>\$$block) {\n"
						. "\$$block"."['ROWCNT']=\$rowcnt;\n"
						. "\$$block"."['ALTROW']=\$rowcnt%2;\n"
						. "\$$block"."['ROWBIT']=\$rowcnt%2;\n"
						. "\$_obj=&\$$block;\n?>";
				$page  =  str_replace("<!-- BEGIN $tag -->",  $code,  $page);
			}
		}

		if (preg_match_all('/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+)[ ]*([!=<>]+)[ ]*(["]?[^"]*["]?) -->/', $page, $var))
		{
			foreach ($var[2] as $cnt => $tag)
			{
				list($parent, $block)  =  $this->var_name($tag);
				$cmp   =  $var[3][$cnt];
				$val   =  $var[4][$cnt];
				$else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
				if ($cmp == '=')
				{
					$cmp  =  '==';
				}
				
				if (preg_match('/"([^"]*)"/',$val,$matches))
				{
					$code  =  "<?php\n$else"."if (\$$parent"."['$block'] $cmp \"".$matches[1]."\"){\n?>";
				}
				elseif (preg_match('/([^"]*)/',$val,$matches))
				{
					list($parent_right, $block_right)  =  $this->var_name($matches[1]);
					$code  =  "<?php\n$else"."if (\$$parent"."['$block'] $cmp \$$parent_right"."['$block_right']){\n?>";
				}
				
				$page  =  str_replace($var[0][$cnt],  $code,  $page);
			}
		}

		if (preg_match_all('/<!-- (ELSE)?IF ([a-zA-Z0-9_.]+) -->/', $page, $var))
		{
			foreach ($var[2] as $cnt => $tag)
			{
				$else  =  ($var[1][$cnt] == 'ELSE') ? '} else' : '';
				list($parent, $block)  =  $this->var_name($tag);
				$code  =  "<?php\n$else"."if (!empty(\$$parent"."['$block'])){\n?>";
				$page  =  str_replace($var[0][$cnt],  $code,  $page);
			}
		}

		if (preg_match_all('/{([a-zA-Z0-9_. >]+)}/', $page, $var))
		{
			foreach ($var[1] as $fulltag)
			{
				list($cmd, $tag)  =  $this->cmd_name($fulltag);

				list($block, $skalar)  =  $this->var_name($tag);
				$code  =  "<?php\n$cmd \$$block"."['$skalar'];\n?>\n";
				$page  =  str_replace('{'.$fulltag.'}',  $code,  $page);
			}
		}

		if (preg_match_all('/<"([a-zA-Z0-9_.]+)">/', $page, $var))
		{
			foreach ($var[1] as $tag)
			{
				list($block, $skalar)  =  $this->var_name($tag);
				$code  =  "<?php\necho gettext('$skalar');\n?>\n";
				$page  =  str_replace('<"'.$tag.'">',  $code,  $page);
			}
		}

		if (strlen($compiled_template_filename))
		{
	        if ($hd  =  @fopen($compiled_template_filename,  "w"))
	        {
		        fwrite($hd,  $page);
		        fclose($hd);
		        return true;
		    }
		    else
		    {
		    	$this->error  =  "Could not write compiled file.";
		        return false;
		    }
		}
		else
		{
			return $page;
		}
	}

	function var_name($tag)
	{
		$parent_level  =  0;
		while (substr($tag, 0, 7) == 'parent.')
		{
			$tag  =  substr($tag, 7);
			$parent_level++;
		}
		if (substr($tag, 0, 4) == 'top.')
		{
			$obj  =  '_stack[0]';
			$tag  =  substr($tag,4);
		}
		elseif ($parent_level)
		{
			$obj  =  '_stack[$_stack_cnt-'.$parent_level.']';
		}
		else
		{
			$obj  =  '_obj';
		}
		while (is_int(strpos($tag, '.')))
		{
			list($parent, $tag)  =  explode('.', $tag, 2);
			if (is_numeric($parent))
			{
				$obj  .=  "[" . $parent . "]";
			}
			else
			{
				$obj  .=  "['" . $parent . "']";
			}
		}
		$ret = array($obj, $tag);
		return $ret;
	}

	function cmd_name($tag)
	{
		if (preg_match('/^(.+) > ([a-zA-Z0-9_.]+)$/', $tag, $tagvar))
		{
			$tag  =  $tagvar[1];
			list($newblock, $newskalar)  =  $this->var_name($tagvar[2]);
			$cmd  =  "\$$newblock"."['$newskalar']=";
		}
		else
		{
			$cmd  =  "echo";
		}
		$ret = array($cmd, $tag);
		return $ret;
	}

	function count_subtemplates()
	{
		preg_match_all('/<!-- INCLUDE ([a-zA-Z0-9_.]+) -->/', $this->template, $tvar);
		$count_subtemplates = count($tvar[1]);
		$ret = intval($count_subtemplates);
		return $ret;
	}
}
?>